package com.sloan.community.exception;

import com.sloan.community.common.res.Result;
import com.sloan.community.util.JsonUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.MessageSourceResolvable;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.validation.method.ParameterValidationResult;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.support.WebExchangeBindException;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.method.annotation.HandlerMethodValidationException;
import org.springframework.web.reactive.DispatcherHandler;
import org.springframework.web.server.ResponseStatusException;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.util.Objects;

import static com.sloan.community.common.Constant.ERROR_MSG;
import static org.springframework.web.reactive.HandlerMapping.BEST_MATCHING_HANDLER_ATTRIBUTE;

@Slf4j
@ControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(value = CommunityException.class)
    public Mono<Void> communityExceptionHandler(ServerWebExchange exchange, CommunityException ex) {
        log.error(ex.getMessage());
        return this.handleException(exchange, ex, ex.getMessage());
    }

    /**
     * 适合单独对象校验，在一个实体类中加@NotBlank,@NotNull有关注解的
     */
    @ExceptionHandler(value = WebExchangeBindException.class)
    public Mono<Void> webExchangeBindExceptionHandler(ServerWebExchange exchange, WebExchangeBindException ex) {
        log.error(ex.getMessage());
        return this.handleException(exchange, ex,
                ex.getBindingResult().getAllErrors().get(0).getDefaultMessage());
    }

    /**
     * 方法列表参数校验【在方法上加@NotNull,@Min这种注解的】，类似那种@RequestParam,@PathVariable上有关 @Min,@Max ,@NotBlank注解
     */
    @ExceptionHandler(value = HandlerMethodValidationException.class)
    public Mono<Void> methodValidationExceptionHandler(ServerWebExchange exchange, HandlerMethodValidationException ex) {
        log.error(ex.getMessage());
        for (ParameterValidationResult result : ex.getAllValidationResults()) {
            for (MessageSourceResolvable error : result.getResolvableErrors()) {
                Object[] arguments = error.getArguments();
                String defaultMessage = error.getDefaultMessage();

                log.info("有关参数：{},错误:{}", arguments, defaultMessage);
                return this.handleException(exchange, ex, defaultMessage);
            }
        }
        return this.handleException(exchange, ex, ex.getMessage());
    }

    @ExceptionHandler(value = Exception.class)
    public Mono<Void> exceptionHandler(ServerWebExchange exchange, Exception ex) {
        log.error(ex.getMessage());
        return this.handleException(exchange, ex, ex.getMessage());
    }


    public Mono<Void> handleException(ServerWebExchange exchange, Exception ex, String msg) {
        ServerHttpResponse response = exchange.getResponse();
        exchange.getAttributes().put(ERROR_MSG, msg);

        Object object = exchange.getAttributes().get(BEST_MATCHING_HANDLER_ATTRIBUTE);
        if (object instanceof HandlerMethod handlerMethod) {
            if (exchange.getRequest().getHeaders().getAccept().contains(MediaType.APPLICATION_JSON)
                    || AnnotatedElementUtils.hasAnnotation(handlerMethod.getMethod(), ResponseBody.class)
                    || AnnotatedElementUtils.hasAnnotation(handlerMethod.getClass(), ResponseBody.class)) {

                String json = JsonUtils.serialize(Result.error(msg));
                // 解决浏览器乱码
                response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
                return response.writeWith(Mono.just(response.bufferFactory()
                        .wrap(json.getBytes(StandardCharsets.UTF_8))));
            }

            HttpStatus status = determineHttpStatus(ex);

            if (status.is4xxClientError()) {
                return this.forwardRequest(exchange, "/err/4xx.html");
            }
            return this.forwardRequest(exchange, "/err/5xx.html");
        }

        // 如果是因为静态资源出现问题，直接走
        return this.forwardRequest(exchange, "/err/4xx.html");
    }

    private HttpStatus determineHttpStatus(Throwable error) {
        if (error instanceof CommunityException communityException) {
            return communityException.getStatus();
        }

        if (error instanceof ResponseStatusException responseStatusException) {
            HttpStatus httpStatus = HttpStatus.resolve(responseStatusException.getStatusCode().value());
            if (httpStatus != null) {
                return httpStatus;
            }
        }
        return HttpStatus.INTERNAL_SERVER_ERROR;
    }

    private Mono<Void> forwardRequest(ServerWebExchange exchange, String uri) {
        ServerHttpRequest newRequest = exchange.getRequest().mutate()
                .uri(URI.create(uri))
                .method(HttpMethod.GET)
                .build();
        ServerWebExchange newExchange = exchange.mutate().request(newRequest).build();
        DispatcherHandler webHandler = Objects.requireNonNull(exchange.getApplicationContext())
                .getBean(DispatcherHandler.class);
        return webHandler.handle(newExchange);
    }

    private Mono<Void> redirectRequest(ServerWebExchange exchange, String uri) {
        ServerHttpResponse response = exchange.getResponse();
        response.setStatusCode(HttpStatus.FOUND);
        response.getHeaders().setLocation(URI.create(uri));
        return response.setComplete();
    }


//    @ExceptionHandler(value = Exception.class)
//    public Mono<RedirectView> communityExceptionHandler2(Exception ex, ServerWebExchange exchange) {
//        for (Throwable throwable : ex.getSuppressed()) {
//            throwable.printStackTrace();
//        }
//        log.error(ex.getMessage());
//        // 重定向必须定义路径跳转 类似 @GetMappings
//        Rendering build = Rendering.redirectTo("/4xx.html").build();
//        return Mono.just(new RedirectView("/4xx.html"));
//    }

//    @ExceptionHandler(value = Exception.class)
//    public Mono<Rendering> communityExceptionHandler2(Exception ex, ServerWebExchange exchange) {
//        for (Throwable throwable : ex.getSuppressed()) {
//            throwable.printStackTrace();
//        }
//        log.error(ex.getMessage());
//
//        Rendering build = Rendering.view("4xx")
//                .build();
//        return Mono.just(build);
//    }
}
